package me.yuxiaoyao.lock.interceptor;

import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author kerryzhang on 2020/12/22
 */


public abstract class AbstractFallbackDistributedLockOperationSource implements DistributedLockOperationSource {
    /**
     * 方法解析过的注解缓存
     */
    private final Map<Object, Collection<DistributedLockOperation>> lockCaches = new ConcurrentHashMap<>(1024);

    private static final Collection<DistributedLockOperation> NULL_LOCK_LIST = Collections.emptyList();

    @Override
    public Collection<DistributedLockOperation> getLockOperations(Method method, Class<?> targetClass) {
        if (method.getDeclaringClass() == Object.class) {
            return null;
        }
        Object cacheKey = getCacheKey(method, targetClass);
        // 一个方法上的多个注解
        Collection<DistributedLockOperation> cacheLockOperations = lockCaches.get(cacheKey);

        if (cacheLockOperations != null) {
            return cacheLockOperations == NULL_LOCK_LIST ? null : cacheLockOperations;
        }
        // 解析方法上的注解,由于注解可能写在类上，所以对整个类的所有方法进行缓存
        Collection<DistributedLockOperation> lockOps = computeDistributedLockOperations(method, targetClass);
        if (lockOps != null) {
            this.lockCaches.put(cacheKey, lockOps);
        } else {
            // 类上的其他方法
            this.lockCaches.put(cacheKey, NULL_LOCK_LIST);
        }
        return lockOps;
    }

    protected Collection<DistributedLockOperation> computeDistributedLockOperations(Method method, Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        }
        // The method may be on an interface, but we need attributes from the target class.
        // If the target class is null, the method will be unchanged.
        Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

        // First try is the method in the target class.
        Collection<DistributedLockOperation> opDef = findDistributedLockOperations(specificMethod);
        if (opDef != null) {
            return opDef;
        }

        // Second try is the caching operation on the target class.
        opDef = findDistributedLockOperations(specificMethod.getDeclaringClass());
        if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
            return opDef;
        }

        if (specificMethod != method) {
            // Fallback is to look at the original method.
            opDef = findDistributedLockOperations(method);
            if (opDef != null) {
                return opDef;
            }
            // Last fallback is the class of the original method.
            opDef = findDistributedLockOperations(method.getDeclaringClass());
            if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
                return opDef;
            }
        }
        return null;
    }


    protected Object getCacheKey(Method method, Class<?> targetClass) {
        return new MethodClassKey(method, targetClass);
    }

    protected boolean allowPublicMethodsOnly() {
        return false;
    }

    @Nullable
    protected abstract Collection<DistributedLockOperation> findDistributedLockOperations(Method clazz);

    @Nullable
    protected abstract Collection<DistributedLockOperation> findDistributedLockOperations(Class<?> clazz);
}
